package edu.cmu.javadb;

import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.Collection;

import edu.cmu.javadb.annotations.Link;
import edu.cmu.javadb.exceptions.NoAttributeException;

public class Attribute {
	private Class<?> fieldType;
	private Class<?> classType;
	private String attName;
	private String fieldName;
	private Method getter;
	private Method setter;
	private VCollection<? extends BaseRecord> linked = null;
	private boolean isCollectionType = false;

	private void initialize(Field f) throws NoAttributeException {
		this.fieldName = f.getName();
		this.attName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
		this.classType = f.getDeclaringClass();
		try {
			this.fieldType = f.getType();
			this.setter = classType.getMethod("set" + attName, fieldType);
			this.getter = classType.getMethod("get" + attName);

			for (Annotation ann : f.getDeclaredAnnotations()) {
				if (ann instanceof Link) {
					isCollectionType = Collection.class.isAssignableFrom(this.fieldType);
					linked = VCollection.getInstance(((Link) ann).value());
				}
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO probably allow attribute with no setter or getter
		}
	}

	public Attribute(Field f) throws NoAttributeException {
		initialize(f);
	}

	public Attribute(Class<?> classType, String name) throws NoAttributeException {
		try {
			initialize(classType.getDeclaredField(name.substring(0, 1).toLowerCase() + name.substring(1)));
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			throw new NoAttributeException();
		}
	}

	public Class<?> getType() {
		return fieldType;
	}

	public String getName() {
		return attName;
	}

	public void setValue(Object o, Object val) {
		try {
			setter.invoke(o, val);
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	// TODO: this is not a secure method
	public void setFieldDirectly(Object o, Object val) {
		try {
			Field f = classType.getDeclaredField(fieldName);

			boolean acc = f.isAccessible();
			f.setAccessible(true);

			f.set(o, val);

			f.setAccessible(acc);
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public <T> T getValue(Object o) {
		try {
			return (T)getter.invoke(o);
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}

	public <T extends BaseRecord> VCollection<T> getLinked() {
		return (VCollection<T>) linked;
	}

	public void setLinked(VCollection<?> linked) {
		this.linked = linked;
	}
	
	public <T2 extends BaseRecord> void addToLink(Object r) {
		T2 v = this.getValue(r);
		if (v == null)
			return;
		VCollection<T2> link = getLinked();
		if (v instanceof Collection) {
			link.addAll((Collection)v);
		} else {
			link.add(v);
		}
		//TODO link.addBackLink(r);
	}
}
